/*
 * Created on Apr 18, 2005
 */
package org.vectrics.recruiter.job;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.TreeSet;

import org.apache.log4j.Logger;
import org.vectrics.SystemException;
import org.vectrics.recruiter.RecruitServiceLocator;
import org.vectrics.skill.Skill;
import org.vectrics.skill.SkillRating;
import org.vectrics.skill.SkillScoringMethod;
import org.vectrics.user.Person;

public class JobCandidateSkillEngine {
	private transient final Logger log = Logger.getLogger(this.getClass());

	public JobCandidateSkillEngine() {
		super();
	}
	
	public void findCandidates(Job job) {
	}
	
	public CandidateScoringResult calculatePersonScore(Job job, Person candidate, boolean persistResult) {
		CandidateScoringResult candidateScoringResult = new CandidateScoringResult(job, candidate);
		
		JobSkillRatingSet jobSkillRatingSet =  RecruitServiceLocator.getSkillProvider().findJobSkillSet(job);
		JobCandidateSkillCalculation calculation = new JobCandidateSkillCalculation(candidate, job, jobSkillRatingSet);
		long score = calculateCandidateSkillScore(calculation, jobSkillRatingSet.getRootRating());
		candidateScoringResult.setScore(score);
		
		if (persistResult) {
			JobCandidateResult result = RecruitServiceLocator.getSkillProvider().findJobCandidateResult(job, candidate);
			if (result == null) {
				result = new JobCandidateResult();
				result.setJob(job);
				result.setPerson(candidate);
			}
			result.setScore(new Long(score));
			RecruitServiceLocator.getSkillProvider().updateJobCandidateResult(result);
		}
		return candidateScoringResult;
	}
	
	
	public long calculateCandidateSkillScore(JobCandidateSkillCalculation calculation, 
				JobSkillRating jobSkillRating) {
		long score = 0;
		
		// Store all child job ratings for candidate in calculation.
		Iterator iter = jobSkillRating.getChildSkillRatings().iterator();
		while (iter.hasNext()) {
			JobSkillRating childJobSkillRating = (JobSkillRating)iter.next();
			calculateCandidateSkillScore(calculation, childJobSkillRating);
		}
		
		log.debug("Finding score for job skill rating: " + jobSkillRating.getDescription());

		JobCandidateSkill jobCandidateSkill = calculation.addJobCandidateSkill(jobSkillRating);
		jobCandidateSkill.setJobRating(jobSkillRating);
		long maximumScore = 0;
		long childTotalScore = 0;
		if (jobSkillRating.getSkill() != null) {
			log.debug("Job skill rating is NOT a parent of other skills - skill = " + jobSkillRating.getSkill().getCode());
			score = this.calculateBranchScore(calculation, jobCandidateSkill);
		} else {
			TreeSet additiveScores = new TreeSet(new JobCandidateCalculationChildScoreCompare());
			Iterator childIter = jobSkillRating.getChildSkillRatings().iterator();
			while (childIter.hasNext()) {
				JobSkillRating childJobSkillRating = (JobSkillRating)childIter.next();
				log.debug("Child rating: " + childJobSkillRating.getDescription());
				JobCandidateSkill childJobCandidateSkill = calculation.findJobCandidateSkill(childJobSkillRating);
				long childScore = childJobCandidateSkill.getScore();
				long childWeight = childJobSkillRating.getLevel().longValue();
				long maxLevel = 100;
				long maxChildScore = (maxLevel * childWeight) / 100;
				log.debug("Child score: " + childScore);
				log.debug("Child weight: " + childWeight);
				log.debug("Max child score: " + maxChildScore);
				childScore = (childScore * childJobSkillRating.getLevel().longValue()) / 100;
				if (jobSkillRating.getChildConjunctionType() == JobSkillRating.CONJUNCTION_ADDITIVE) {
					if (childScore > maxChildScore) {
						throw new SystemException("Score for child rating was greater than max possible score for child");
					}
					//childTotalScore +=  (childWeight * childScore) / 100;
					//maximumScore += (maxChildScore * childWeight) / 100;
					JobCandidateCalculationChildScore childCalcScore = 
							new JobCandidateCalculationChildScore();
					childCalcScore.setChildScore(new Long((childWeight * childScore) / 100));
					childCalcScore.setMaximumScore(new Long((maxChildScore * childWeight) / 100));
					additiveScores.add(childCalcScore);
				} else {
					if (childScore > childTotalScore) {
						childTotalScore = childScore;
					}
					if (maxChildScore > maximumScore) {
						maximumScore = maxChildScore;
					}
				}
			} // End while
			
			if (jobSkillRating.getChildConjunctionType() == JobSkillRating.CONJUNCTION_ADDITIVE) {
				List additiveScoreList = new ArrayList();
				List additiveMaximumScoreList = new ArrayList();
				additiveScoreList.addAll(additiveScores);
				long decayLevel = 100;
				maximumScore = 0;
				childTotalScore = 0;
				for (int i = additiveScoreList.size() - 1; i >= 0; i--) {
					JobCandidateCalculationChildScore calcChildScore =
						(JobCandidateCalculationChildScore)additiveScoreList.get(i);
					Long childScore = calcChildScore.getChildScore();
					Long maxChildScore = calcChildScore.getMaximumScore();
					
					maximumScore = maximumScore + (decayLevel * maxChildScore.longValue()) / 100;
					childTotalScore = childTotalScore + ((decayLevel * childScore.longValue()) / 100);
					log.debug("Decay level: " + decayLevel);
					log.debug("Additive Child score: " + childScore);
					log.debug("Additive Max child score: " + maxChildScore);
					decayLevel = decayLevel - ((jobSkillRating.getAdditiveDecay().longValue() * (100 - jobSkillRating.getAdditiveDecay().longValue())) / 100);
					if (decayLevel > 100) {
						throw new SystemException("decayLevel should never be greater than 100");
					} else if (decayLevel < 0) {
						throw new SystemException("decayLevel should never be less than 0");
					}
				}
			}
			log.debug("Total Maximum score = " + maximumScore);
			log.debug("Child total score = " + childTotalScore);
			score = (100 * childTotalScore)/ maximumScore;
		}
		log.debug("Score for job skill rating (" + jobSkillRating.getDescription() + "} is: " + score);
		return(score);
	}

	
	public long calculateBranchScore(JobCandidateSkillCalculation calculation, JobCandidateSkill jobCandidateSkill) {
		JobSkillRating jobRating = jobCandidateSkill.getJobRating();
		long branchScore = 0;
		if (jobRating.getSkill().getScoringMethod().getCode().equals(
						SkillScoringMethod.SCORING_METHOD_NONE)) {
			branchScore = getBranchScore(calculation, jobRating.getSkill()).getNormalizedScore();
			jobCandidateSkill.setScore(branchScore);
		} else {
			branchScore = jobCandidateSkill.getScore();
		}
		return(branchScore);
	}
	

	public BranchScore getBranchScore(JobCandidateSkillCalculation calculation, Skill parentSkill) {
		log.debug("getBranchScore() - skill = " + parentSkill.getCode());
		BranchScore scoreAndMaximum = new BranchScore();
		Iterator childSkillIter = parentSkill.getChildren().iterator();
		long childTotalScore = 0;
		long maximumScore = 0;
		TreeSet additiveScores = new TreeSet(new JobCandidateCalculationChildScoreCompare());
		while (childSkillIter.hasNext()) {
			Skill childSkill = (Skill)childSkillIter.next();
			log.debug("getBranchScore() - skill = " + parentSkill.getCode()
						+ " ||  child skill: " + childSkill.getCode());
			//BranchScore childScoreAndMax = null;
			long childScore = 0;
			long maxChildScore = 100;
			long childWeight = childSkill.getWeight();
			
			if (childSkill.getScoringMethod().getCode().equals(SkillScoringMethod.SCORING_METHOD_NONE)) {
				BranchScore childScoreAndMax = getBranchScore(calculation, childSkill);
				childScore = childScoreAndMax.getNormalizedScore();
				log.debug("Child skill not ratable (" + childSkill.getCode() 
						+ ") - branch score = " + childScore);
			} else {
				SkillRating userRating = calculation.getUserSkillSet().findSkillRating(childSkill);
				if (userRating != null) {
					childScore = userRating.getScore();
					log.debug("User rating for child skill (" + childSkill.getCode() 
							+ "): " + userRating.getScore());
				} else {
					log.debug("User rating for child skill(" + childSkill.getCode() 
							+ ") was not found - null");
				}
			}
			
			
			if (parentSkill.getChildConjunctionType() == Skill.CONJUNCTION_ADDITIVE) {
				if (childScore > maxChildScore) {
					throw new SystemException("Score for child rating was greater than max possible score for child");
				}
				//childTotalScore +=  (childWeight * childScore) / 100;
				//maximumScore += (maxChildScore * childWeight) / 100;
				
				
				JobCandidateCalculationChildScore childCalcScore = 
						new JobCandidateCalculationChildScore();
				childCalcScore.setChildScore(new Long((childWeight * childScore) / 100));
				childCalcScore.setMaximumScore(new Long((childWeight * maxChildScore) / 100));
				additiveScores.add(childCalcScore);
			} else {
				if (childScore > childTotalScore) {
					childTotalScore = childScore;
				}
				if (maxChildScore > maximumScore) {
					maximumScore = maxChildScore;
				}
			}
		}
		
		if (parentSkill.getChildConjunctionType() == Skill.CONJUNCTION_ADDITIVE) {
			List additiveScoreList = new ArrayList();
			List additiveMaximumScoreList = new ArrayList();
			additiveScoreList.addAll(additiveScores);
			long decayLevel = 100;
			maximumScore = 0;
			childTotalScore = 0;
			for (int i = additiveScoreList.size() - 1; i >= 0; i--) {
				JobCandidateCalculationChildScore calcChildScore =
					(JobCandidateCalculationChildScore)additiveScoreList.get(i);
				Long childScore = calcChildScore.getChildScore();
				Long maxChildScore = calcChildScore.getMaximumScore();
				
				maximumScore = maximumScore + (decayLevel * maxChildScore.longValue()) / 100;
				childTotalScore = childTotalScore + ((decayLevel * childScore.longValue()) / 100);
				log.debug("Decay level: " + decayLevel);
				log.debug("Additive Child score: " + childScore);
				log.debug("Additive Max child score: " + maxChildScore);
				decayLevel = decayLevel - ((parentSkill.getAdditiveDecay().longValue() * (100 - parentSkill.getAdditiveDecay().longValue())) / 100);
				if (decayLevel > 100) {
					throw new SystemException("decayLevel should never be greater than 100");
				} else if (decayLevel < 0) {
					throw new SystemException("decayLevel should never be less than 0");
				}
			}
		}
		
		log.debug("Total Maximum score = " + maximumScore);
		log.debug("Child total score = " + childTotalScore);
		scoreAndMaximum.setScore(childTotalScore);
		scoreAndMaximum.setMaximumScore(maximumScore);
		return(scoreAndMaximum);
	}
	
	
}
